package com.zelix.analy;

import com.arthas.client.ArthasClient;
import com.arthas.client.ArthasTool;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import static com.zelix.analy.TTT.getClassLoader;
import static com.zelix.analy.TTT.getKeys;

/**
 * 需要类加载后才准确
 */
public class TTTT {
    static HashMap<String, String> keyMap = getKeys("./file/lesslong.txt");

    public static void main(String[] args) throws IOException {
        JarFile jarFile = null;
        try {
            jarFile = new JarFile("./file/ZKM_bak.jar");
        } catch (IOException e) {
            e.printStackTrace();
        }

        ArthasClient client = ArthasTool.getClient();

        if (jarFile == null || client == null) {
            return;
        }
        String rule = "com/zelix/p8";

        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().endsWith(".class")) {//只分析class文件
                //去除文件名后缀 ".class"
                analy(client, jarFile, entry.getName().substring(0, entry.getName().length() - 6));
            }
        }

        client.disconnect();
    }

    public static void analy(ArthasClient client, JarFile jarFile, String cls) throws IOException {

        //获取类所在的classloader,idea中的一些方法不加classloader去运行,可能找不到类
        String classLoader = getClassLoader(client, jarFile, cls);
        if (classLoader == null) {
            //System.out.println("类 " + cls + " 还未加载,请点击相应功能触发其加载.");
            return;
        }

        String fromat1 = "ognl '#f=@%s@%s,#cls=@%s@class,#obj=@%s@%s(%sL,%sL,#cls),#l=#obj.%s(%sL),#l^#f^%sL'";
        String fromat2 = "ognl '#cls=@%s@class,#obj=@%s@%s(%sL,%sL,#cls),#l=#obj.%s(%sL),#l^%sL'";

        ZipEntry entry = jarFile.getEntry(cls + ".class");

        if (entry != null) {
            ClassReader cr = new ClassReader(jarFile.getInputStream(entry));
            ClassNode cn = new ClassNode();
            cr.accept(cn, ClassReader.EXPAND_FRAMES);

            List<MethodNode> methods = cn.methods;

            //遍历方法
            for (MethodNode mn : methods) {
                String cmd = null;
                InsnList list = mn.instructions;
                for (int i = 0; i < list.size(); i++) {
                    /**
                     * LDC -5025338045761974752L
                     * LDC 9086110606947397886L
                     * INVOKESTATIC java/lang/invoke/MethodHandles.lookup()Ljava/lang/invoke/MethodHandles$Lookup;
                     * INVOKEVIRTUAL java/lang/invoke/MethodHandles$Lookup.lookupClass()Ljava/lang/Class;
                     * INVOKESTATIC com/zelix/sqq.a(JJLjava/lang/Object;)Lcom/zelix/sii;
                     * LDC 55627301121680L
                     * INVOKEINTERFACE com/zelix/sii.a(J)J
                     * GETSTATIC com/zelix/ZKM.a J
                     * LXOR
                     * LDC 22185872119918L
                     * LXOR
                     * LSTORE 1
                     */
                    /**
                     * LDC 3860997231069254442L
                     * LDC 2093728543849920931L
                     * INVOKESTATIC java/lang/invoke/MethodHandles.lookup()Ljava/lang/invoke/MethodHandles$Lookup;
                     * INVOKEVIRTUAL java/lang/invoke/MethodHandles$Lookup.lookupClass()Ljava/lang/Class;
                     * INVOKESTATIC com/zelix/sqq.a(JJLjava/lang/Object;)Lcom/zelix/sii;
                     * LDC 87691935305878L
                     * INVOKEINTERFACE com/zelix/sii.a(J)J
                     * LDC 78966612096674L
                     * LXOR
                     * LSTORE 14
                     */
                    //判断类型
                    if (list.size() >= 10
                            && list.get(i) instanceof LdcInsnNode && list.get(i + 1) instanceof LdcInsnNode
                            && list.get(i + 2) instanceof MethodInsnNode && list.get(i + 3) instanceof MethodInsnNode && list.get(i + 4) instanceof MethodInsnNode
                            && list.get(i + 5) instanceof LdcInsnNode && list.get(i + 6) instanceof MethodInsnNode
                    ) {
                        LdcInsnNode ldc0 = (LdcInsnNode) list.get(i);
                        LdcInsnNode ldc1 = (LdcInsnNode) list.get(i + 1);
                        MethodInsnNode lookup = (MethodInsnNode) list.get(i + 2);
                        MethodInsnNode lookupClass = (MethodInsnNode) list.get(i + 3);
                        MethodInsnNode getObject = (MethodInsnNode) list.get(i + 4);
                        LdcInsnNode ldc5 = (LdcInsnNode) list.get(i + 5);
                        MethodInsnNode getLong = (MethodInsnNode) list.get(i + 6);
                        //判断描述
                        if (lookup.owner.equals("java/lang/invoke/MethodHandles") && lookup.name.equals("lookup") && lookup.desc.equals("()Ljava/lang/invoke/MethodHandles$Lookup;")
                                && lookupClass.owner.equals("java/lang/invoke/MethodHandles$Lookup") && lookupClass.name.equals("lookupClass") && lookupClass.desc.equals("()Ljava/lang/Class;")
                                && getObject.owner.startsWith("com/zelix/") && getObject.desc.startsWith("(JJ") && getObject.desc.contains(getLong.owner)
                                && getLong.owner.startsWith("com/zelix/") && getLong.desc.equals("(J)J")
                        ) {
                            //公共部分判断完,判读不同的地方
                            if (list.get(i + 7) instanceof FieldInsnNode) {
                                if (list.get(i + 8) instanceof InsnNode && list.get(i + 8).getOpcode() == Opcodes.LXOR
                                        && list.get(i + 9) instanceof LdcInsnNode
                                        && list.get(i + 10) instanceof InsnNode && list.get(i + 10).getOpcode() == Opcodes.LXOR
                                        && list.get(i + 11) instanceof VarInsnNode && list.get(i + 11).getOpcode() == Opcodes.LSTORE) {
                                    FieldInsnNode fieldLong = (FieldInsnNode) list.get(i + 7);
                                    LdcInsnNode ldc9 = (LdcInsnNode) list.get(i + 9);
                                    if (fieldLong.owner.startsWith("com/zelix/") && fieldLong.desc.equals("J")) {
                                        cmd = String.format(fromat1, fieldLong.owner.replaceAll("/", "."), fieldLong.name, cls.replaceAll("/", "."), getObject.owner.replaceAll("/", "."), getObject.name, ldc0.cst.toString(), ldc1.cst.toString(), getLong.name, ldc5.cst.toString(), ldc9.cst.toString());
                                        break;
                                    }
                                }

                            } else if (list.get(i + 7) instanceof LdcInsnNode) {
                                LdcInsnNode ldc7 = (LdcInsnNode) list.get(i + 7);
                                if (list.get(i + 8) instanceof InsnNode && list.get(i + 8).getOpcode() == Opcodes.LXOR
                                        && list.get(i + 9) instanceof VarInsnNode && list.get(i + 9).getOpcode() == Opcodes.LSTORE) {
                                    cmd = String.format(fromat2, cls.replaceAll("/", "."), getObject.owner.replaceAll("/", "."), getObject.name, ldc0.cst.toString(), ldc1.cst.toString(), getLong.name, ldc5.cst.toString(), ldc7.cst.toString());
                                    break;
                                }
                            } else {
                                //System.out.println("***********" + cls + "***********");
                                //System.out.println("------------" + mn.name + mn.desc + "------------");
                                //System.out.println(list.get(i + 7));
                            }
                        }
                    }
                }


                //指令遍历完
                if (!keyMap.containsKey(cls + ":" + mn.name + ":" + mn.desc) && cmd != null) {
                    String rtn = client.sendCommand(cmd);
                    System.out.println(cmd);
                    System.out.println(cls + ":" + mn.name + ":" + mn.desc + ":" + getWatchReturnLong(rtn));
                }
            }
        }
    }

    public static String getWatchReturnLong(String rtn) {
        //超时时会返回null
        if (rtn == null) {
            return null;
        }
        String tag = "@Long[";
        int start = rtn.indexOf(tag);
        int end = rtn.indexOf("]", start);
        return rtn.substring(start + tag.length(), end);
    }
}
